package org.dftproject.gedcom.core;

import java.util.List;
import java.util.Set;

import org.dftproject.gedcom.core.persistence.GedcomPersonFactory;
import org.dftproject.genesis.core.persistence.IMemento;
import org.dftproject.genesis.core.persistence.IPersistable;
import org.dftproject.genesis.data.IAttribute;
import org.dftproject.genesis.data.genealogy.GenealogyConstants;
import org.dftproject.genesis.data.genealogy.IEvent;
import org.dftproject.genesis.data.genealogy.IPerson;
import org.dftproject.genesis.data.genealogy.IRole;
import org.dftproject.genesis.data.genealogy.ISource;
import org.dftproject.genesis.data.genealogy.Sex;
import org.dftproject.genesis.data.genealogy.impl.PersonImpl;
import org.dftproject.genesis.data.genealogy.impl.RoleImpl;
import org.dftproject.genesis.data.impl.AttributeImpl;

public class GedcomPerson extends PersonImpl implements IPersistable {

	private final IGedcom gedcom;
	private final String xref;
	private final Line record;
	
	private boolean indiInit = false;
	private boolean famsInit = false;
	
	public GedcomPerson(IGedcom gedcom, String xref, Line record) {
		this.gedcom = gedcom;
		this.xref = xref;
		this.record = record;
	}
	
	public String getId() {
		return xref;
	}
	
	public IGedcom getGedcom() {
		return gedcom;
	}
	
	public Line getRecord() {
		return record;
	}
	
	@Override
	public Set<IAttribute> getAttributes() {
		if (!indiInit)
			initIndi();
		return super.getAttributes();
	}
	
	@Override
	public Set<IAttribute> getAttributes(String name) {
		if (!indiInit)
			initIndi();
		return super.getAttributes(name);
	}
	
	@Override
	public Set<IRole> getRoles() {
		if (!indiInit)
			initIndi();
		if (!famsInit)
			initFams();
		return super.getRoles();
	}
	
	@Override
	public Set<IRole> getRoles(String type) {
		if (!indiInit)
			initIndi();
		if (!famsInit && intersects(new String[] { type }, new String[] { GenealogyConstants.Parent, GenealogyConstants.Father, GenealogyConstants.Mother, GenealogyConstants.Spouse }))
			initFams();
		return super.getRoles(type);
	}
	
	@Override
	public Set<IRole> getRoles(String[] types) {
		if (!indiInit)
			initIndi();
		if (!famsInit && intersects(types, new String[] { GenealogyConstants.Parent, GenealogyConstants.Father, GenealogyConstants.Mother, GenealogyConstants.Spouse }))
			initFams();
		return super.getRoles(types);
	}
	
	protected boolean intersects(Object[] a, Object[] b) {
		for (Object x : a) {
			for (Object y : b) {
				if (x.equals(y))
					return true;
			}
		}
		return false;
	}
	
	protected void initIndi() {
		indiInit = true;
		
		boolean hasBirth = false;
		
		ISource source = gedcom.asSource(xref);
		
		for (Line line : record.getChildren()) {
			if ("NAME".equals(line.getTag())) {
				AttributeImpl attribute = new AttributeImpl(GenealogyConstants.name, new GedcomName(line));
				attribute.addAttribute(new AttributeImpl(GenealogyConstants.source, source));
				addAttribute(attribute);
			} else if ("SEX".equals(line.getTag())) {
				AttributeImpl attribute = null;
				
				String sex = line.getValue();
				if ("M".equalsIgnoreCase(sex))
					attribute = new AttributeImpl(GenealogyConstants.sex, Sex.Male);
				else if ("F".equalsIgnoreCase(sex))
					attribute = new AttributeImpl(GenealogyConstants.sex, Sex.Female);
				
				if (attribute != null) {
					attribute.addAttribute(new AttributeImpl(GenealogyConstants.source, source));
					addAttribute(attribute);
				}
			} else if ("BIRT".equals(line.getTag())) {
				hasBirth = true;

				List<Line> children = line.getChildren("FAMC");
				if (children.isEmpty())
					children = record.getChildren("FAMC");
				if (children.isEmpty()) {
					RoleImpl role = new RoleImpl(GenealogyConstants.Child, this, new BirthEvent(line, this, gedcom, null)); 
					role.addAttribute(new AttributeImpl(GenealogyConstants.source, source));
					addRole(role);
				}
				
				for (Line child : children) {
					if (child.getPointer() != null) {
						RoleImpl role = new RoleImpl(GenealogyConstants.Child, this, new BirthEvent(line, this, gedcom, child.getPointer())); 
						role.addAttribute(new AttributeImpl(GenealogyConstants.source, source));
						addRole(role);
					}
				}
			} else if ("DEAT".equals(line.getTag())) {
				Event event = new Event(line);
				RoleImpl role = new RoleImpl(GenealogyConstants.Deceased, this, event);
				role.addAttribute(new AttributeImpl(GenealogyConstants.source, source));
				addRole(role);
				event.addRole(role);
			} else if ("CHR".equals(line.getTag())) {
				Event event = new Event(line);
				RoleImpl role = new RoleImpl(GenealogyConstants.Christened, this, event);
				role.addAttribute(new AttributeImpl(GenealogyConstants.source, source));
				addRole(role);
				event.addRole(role);
			} else if ("BURI".equals(line.getTag())) {
				Event event = new Event(line);
				RoleImpl role = new RoleImpl(GenealogyConstants.Buried, this, event);
				role.addAttribute(new AttributeImpl(GenealogyConstants.source, source));
				addRole(role);
				event.addRole(role);
			} else if ("RESI".equals(line.getTag())) {
				Event event = new Event(line);
				RoleImpl role = new RoleImpl(GenealogyConstants.Resided, this, event);
				role.addAttribute(new AttributeImpl(GenealogyConstants.source, source));
				addRole(role);
				event.addRole(role);
			} else if ("CENS".equals(line.getTag())) {
				Event event = new Event(line);
				RoleImpl role = new RoleImpl(GenealogyConstants.Censused, this, event);
				role.addAttribute(new AttributeImpl(GenealogyConstants.source, source));
				addRole(role);
				event.addRole(role);
			} else if ("IMMI".equals(line.getTag())) {
				Event event = new Event(line);
				RoleImpl role = new RoleImpl(GenealogyConstants.Immigrant, this, event);
				role.addAttribute(new AttributeImpl(GenealogyConstants.source, source));
				addRole(role);
				event.addRole(role);
			} else if ("EVEN".equals(line.getTag())) {
				Line type = line.getChild("TYPE");
				if (type != null && "Ancestral File Number".equalsIgnoreCase(type.getValue())) {
					Line afn = line.getChild("PLAC");
					if (afn != null) {
						AttributeImpl attribute = new AttributeImpl(GenealogyConstants.afn, afn.getValue());
						attribute.addAttribute(new AttributeImpl(GenealogyConstants.source, source));
						addAttribute(attribute);
					}
				}
			} else if ("_UID".equals(line.getTag())) {
				AttributeImpl attribute = new AttributeImpl(GenealogyConstants.uid, line.getValue());
				attribute.addAttribute(new AttributeImpl(GenealogyConstants.source, source));
				addAttribute(attribute);
			} else if ("NOTE".equals(line.getTag())) {
				AttributeImpl attribute;
				if (line.getPointer() != null)
					attribute = new AttributeImpl(GenealogyConstants.note, new Note(gedcom, line.getPointer()));
				else
					attribute = new AttributeImpl(GenealogyConstants.note, new Note(line));
				
				attribute.addAttribute(new AttributeImpl(GenealogyConstants.source, source));
				addAttribute(attribute);
			}
			
			// TODO: BAPL, ENDL, SLGC, ALIA
		}
		
		if (!hasBirth) {
			/*
			 * The birth event includes the relationship parents and child; if
			 * there is no explicit BIRT event, but there is a FAMC, then create
			 * an implicit birth event
			 */
			
			for (Line child : record.getChildren("FAMC")) {
				if (child.getPointer() != null) {
					RoleImpl role = new RoleImpl(GenealogyConstants.Child, this, new BirthEvent(null, this, gedcom, child.getPointer())); 
					role.addAttribute(new AttributeImpl(GenealogyConstants.source, source));
					addRole(role);
				}
			}
		}
	}
	
	protected void initFams() {
		famsInit = true;
		
		for (Line line : record.getChildren("FAMS")) {
			Family family = gedcom.getFamily(line.getPointer());
			if (family != null) {
				// Add a spouse role
				
				Event marriageEvent = family.getMarriage();
				if (marriageEvent != null)
					addRole(new RoleImpl(GenealogyConstants.Spouse, this, marriageEvent));
				
				// Add a parent role for each birth event
				
				String roleType = GenealogyConstants.Father;
				if (this.equals(family.getMother()))
					roleType = GenealogyConstants.Mother;
				
				IPerson[] children = family.getChildren();
				for (IPerson child : children) {
					Set<IRole> childRoles = child.getRoles(GenealogyConstants.Child);
					for (IRole childRole : childRoles) {
						IEvent event = childRole.getEvent();
						if (event != null) {
							Set<IRole> roles = event.getRoles(roleType);
							for (IRole role : roles) {
								if (this.equals(role.getPerson()))
									addRole(new RoleImpl(roleType, this, event));
							}
						}
					}
				}
			}
		}
	}

	public String getFactoryId() {
		return GedcomPersonFactory.ID;
	}

	public void saveState(IMemento memento) {
		IMemento child = memento.createChild("gedcom");
		gedcom.saveState(child);
		memento.putString("gedcomFactoryId", gedcom.getFactoryId());
		memento.putString("xref", xref);
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((xref == null) ? 0 : xref.hashCode());
		result = prime * result + ((gedcom == null) ? 0 : gedcom.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		final GedcomPerson other = (GedcomPerson) obj;
		if (xref == null) {
			if (other.xref != null)
				return false;
		} else if (!xref.equals(other.xref))
			return false;
		if (gedcom == null) {
			if (other.gedcom != null)
				return false;
		} else if (!gedcom.equals(other.gedcom))
			return false;
		return true;
	}

}
